package eitwitter.evaluation;

import eitwitter.learning.Learning;
import eitwitter.storage.Category;
import eitwitter.storage.CostsMatrix;
import eitwitter.storage.Tweet;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.EnumMap;

/**
 * Evaluation se servant de la classe Learning et prenant en compte les couts
 * des erreurs
 * @author AH & PJ
 */
public class CostsEvaluation implements Evaluation{

	/** Reference vers la classe d'apprentissage */
	private Learning learning;
	
	/** Reference vers la classe contenant les couts */
	private CostsMatrix costsMatrix;
	
	/** Difference en pourcentage a partir duquel changer la categorie */
	private double percentDifference;
	
	/**
	 * Constructeur de l'evaluation simple
	 * @param learning Reference vers la classe d'apprentissage completee
	 */
	public CostsEvaluation(Learning learning, double percentDifference) {
		this.learning			= learning;
		this.costsMatrix		= CostsMatrix.getInstance();
		this.percentDifference	= percentDifference;
	}

	/**
	 * Determine la categorie du tweet en comparant les inferences suivant les 
	 * categories sur lesquelles s'est fait l'apprentissage
	 * @param tweet Tweet dont on veut determiner la categorie
	 */
	@Override
	public void computeTweetCategory(Tweet tweet) {

		// Contiendra le resultat de l'analyse
		Category result = null;

		// Calcul des inferences pour chaque categorie
		EnumMap<Category, Double> inferences = new EnumMap<Category, Double>(Category.class);

		// Inference maximale trouvee
		double max = Double.NEGATIVE_INFINITY;
		
		// Parcours de toutes les categories existantes
		for(Category c : Category.values()){

			// Calcul de l'inférence pour la catégorie
			double inference = learning.computeInference(tweet, c);
			
			// Stockage pour debogage surtout
			inferences.put(c, inference);

			// Recherche de l'inference maximale
			if(inference > max){
				max = inference;
				result = c;
			}
		}

		// Dans le cas des categories qui sont les plus couteuses si erreur
		if(result != Category.NEUTRAL && result != Category.IRRELEVANT){
			
			// On calcule la difference entre les inferences
			double neutralDif = Math.abs(max - inferences.get(Category.NEUTRAL));
			double irrelevantDif = Math.abs(max - inferences.get(Category.IRRELEVANT));
			
			// On prend la plus petite des deux differences
			if(neutralDif < irrelevantDif){
				
				// Si l'inference decidee est proche de l'inferance neutre
				// On regarde si cette difference est inferieure a un certain pourcentage
				if(neutralDif < percentDifference * Math.abs(max)){
					// On change la categorie pour minimiser les couts
					result = Category.NEUTRAL;
				}
				
			} else {
			
				// Si l'inference decidee est proche de l'inferance neutre
				// On regarde si cette difference est inferieure a un certain pourcentage
				if(irrelevantDif < percentDifference * Math.abs(max)){
					// On change la categorie pour minimiser les couts
					result = Category.IRRELEVANT;
				}
				
			}
		}
		
		// Mise a jour de la categorie du tweet
		tweet.setCategory(result);
	}

	@Override
	public EnumMap<Category, EnumMap<Category, Integer>> computeConfusionMatrix(File file) {
		// Matrice de confusion
		EnumMap<Category, EnumMap<Category, Integer>> matrix = new EnumMap<Category, EnumMap<Category,Integer>>(Category.class);
		// Initialisation de toutes les categories réelles possibles
		for(Category category : Category.values()){
			EnumMap<Category, Integer> tmp = new EnumMap<Category, Integer>(Category.class);
			// Initialisation de toutes les categories évaluées possibles
			for(Category category2 : Category.values()){
				tmp.put(category2, 0);
			}
			// Ajout à la matrice
			matrix.put(category, tmp);
		}

		try {
			// Ouverture du fichier
			InputStream ips = new FileInputStream(file);
			InputStreamReader ipsr = new InputStreamReader(ips);
			BufferedReader br = new BufferedReader(ipsr);

			// Traitement de la ligne en cours
			String line;
			while ((line=br.readLine())!=null){
				// Creation de l'objet Tweet pour la ligne lue
				Tweet t = Tweet.createFromString(line);
				// Récupération de la vraie catégorie
				Category trueCategory = t.getCategory();

				// Evaluation de la catégorie
				this.computeTweetCategory(t);
				// Récupération de la catégorie évaluée
				Category evalCategory = t.getCategory();

				// Modification de la matrice
				EnumMap<Category, Integer> tmp = matrix.get(trueCategory);
				tmp.put(evalCategory, tmp.get(evalCategory)+1);
				matrix.put(trueCategory, tmp);

			}

			// Fermeture des pointeurs
			br.close();
			ips.close();

		} catch (IOException ex) {
			ex.printStackTrace();
		}
		
		return matrix;
	}

	@Override
	public void printTweetCategories(File file) {
		try {
			// Ouverture du fichier
			InputStream ips = new FileInputStream(file);
			InputStreamReader ipsr = new InputStreamReader(ips);
			BufferedReader br = new BufferedReader(ipsr);

			// Traitement de la ligne en cours
			String line;
			while ((line=br.readLine())!=null){
				// Creation de l'objet Tweet pour la ligne lue
				Tweet t = Tweet.createFromString(line);

				// Evaluation de la catégorie
				this.computeTweetCategory(t);
				// Affichage du tweet
				t.toString();

			}

			// Fermeture des pointeurs
			br.close();
			ips.close();

		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

}
